//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2020-10-15     ZhangYihua   first version
//
// Description  : Exponential Moving Average
//                avg_data = (1-1/2^k)*avg_data + (1/2^k)*cur_data
//                         = avg_data + (1/2^k)*(cur_dat - avg_data)
//################################################################################

module u_ema #(     // unsigned Exponential Moving Average, range [0:2^DW-1]
parameter           DW                      = 16,   // data width
parameter           K_MAX                   = 14,   // Maximum of cfg_k, K_MAX>=1
parameter           K_MIN                   =  5,   // Minimum of cfg_k

// the following parameters are calculated automatically
parameter           KW                      = $clog2(K_MAX+1)  // range [K_MIN:K_MAX]
) ( 
input                                       rst_n,
input                                       clk,
input                                       cke,

input                                       cur_vld,
input               [DW-1:0]                cur_data,
output              [DW-1:0]                avg_data,

input                                       cfg_clr,
input               [DW-1:0]                cfg_ini,
input               [KW-1:0]                cfg_k       // range [K_MIN:K_MAX], alpha = 1/2^k
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          ACC_BW                  = DW+K_MAX;

wire                [DW+1-1:0]              diff;
wire                [ACC_BW-1:0]            diff_e;
wire                [ACC_BW-1:0]            acc_c;
reg                 [ACC_BW-1:0]            acc;
wire                [ACC_BW-1:0]            cfg_ini_e;
wire                [ACC_BW-1:0]            cfg_ini_s;


//################################################################################
// main
//################################################################################

//                avg_data = (1-1/2^k)*avg_data + (1/2^k)*cur_data
//                         = avg_data + (1/2^k)*(cur_dat - avg_data)
assign diff = {1'b0, cur_data} - {1'b0, avg_data};

assign diff_e = $signed(diff);
//assign diff_e[0+:DW] = diff[0+:DW];
//assign diff_e[ACC_BW-1:DW] = {(ACC_BW-DW){diff[DW]}};

assign acc_c = acc + diff_e;
always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        acc <=`U_DLY {ACC_BW{1'b0}};
    end else if (cke==1'b1) begin
        if (cfg_clr==1'b1) begin
            acc <=`U_DLY cfg_ini_s;
        end else if (cur_vld==1'b1) begin
            acc <=`U_DLY acc_c;
        end else
            ;
    end else
        ;
end

assign avg_data = (cfg_k>$unsigned(K_MAX)) ? (acc>>K_MAX) :
                  (cfg_k<$unsigned(K_MIN)) ? (acc>>K_MIN) :
                                             (acc>>cfg_k);

assign cfg_ini_e = cfg_ini;
assign cfg_ini_s = (cfg_k>$unsigned(K_MAX)) ? (cfg_ini_e<<K_MAX) :
                   (cfg_k<$unsigned(K_MIN)) ? (cfg_ini_e<<K_MIN) :
                                              (cfg_ini_e<<cfg_k);
//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off


// synopsys translate_on
`endif

endmodule
